#ifndef DATAFLOW_DETAIL_NODE_H
#define DATAFLOW_DETAIL_NODE_H

#include <assert.h>
#include <vector>
#include <set>

#include "Task.h"
#include "NodePtrVector.h"
#include "NodeInputLinkDescriptorVector.h"
#include "NodeOutputLinkDescriptorVector.h"
#include "NodeInputInterface.h"

namespace DataFlow
{

namespace Detail
{

class Node
{
	public:

		typedef void BaseType;
		typedef Node ThisType;

		Node(int id)
			: nid(id)
			, nodeTask(0)
			, inSize(0)
			, inIface(0)
			, outSize(0)
			, subSize(0)

		{
			;
		}

		virtual ~Node(void)
		{
			delete this->inIface;
		}

		void clear(void)
		{
			//this->nid = 0;

			this->nodeTask = 0;

			this->inSize = 0;
			delete this->inIface;
			this->inIface = 0;
			//this->inLinks.clear();
			this->inNodes.clear();

			this->outSize = 0;
			//this->outLinks.clear();
			this->outNodes.clear();

			this->subSize = 0;
		}

		NodeInputLinkDescriptorVector & inputLinks(void)
		{
			return this->inLinks;
		}

		const NodeInputLinkDescriptorVector & inputLinks(void) const
		{
			return this->inLinks;
		}

		NodeOutputLinkDescriptorVector & outputLinks(void)
		{
			return this->outLinks;
		}

		const NodeOutputLinkDescriptorVector & outputLinks(void) const
		{
			return this->outLinks;
		}

		bool setup(Task * task, int inputSize, int outputSize)
		{
			assert(inputSize == int(this->inLinks.size()));

			typedef std::set<Node *>     NodePtrSet;
			typedef NodePtrSet::iterator NodePtrSetIterator;

			this->clear();

			this->nodeTask = task;
			this->inSize   = inputSize;
			this->outSize  = outputSize;

			int k = 0;
			NodePtrSet ns;

			IntVector capacities(size_t(inputSize), 0);

			ns.clear();
			for (size_t i=0; i<this->inLinks.size(); ++i)
			{
				const NodeInputLinkDescriptor & link = this->inLinks[i];
				assert((link.targetPort() >= 0) && (link.targetPort() < inputSize));
				ns.insert(link.sourceNode());
				capacities[link.targetPort()] = link.capacity();
			}

			this->inNodes.resize(ns.size());
			k = 0;
			for (NodePtrSetIterator it=ns.begin(); it!=ns.end(); ++it)
			{
				this->inNodes[k] = (*it);
				k++;
			}

			ns.clear();
			for (size_t i=0; i<this->outLinks.size(); ++i)
			{
				const NodeOutputLinkDescriptor & link = this->outLinks[i];
				assert((link.sourcePort() >= 0) && (link.sourcePort() < outputSize));
				ns.insert(link.targetNode());
			}

			this->outNodes.resize(ns.size());
			k = 0;
			for (NodePtrSetIterator it=ns.begin(); it!=ns.end(); ++it)
			{
				this->outNodes[k] = (*it);
				k++;
			}

			this->inIface = new NodeInputInterface(capacities);

			return true;
		}

		bool isValid(void) const
		{
			return (this->inIface != 0);
		}

		int id(void) const
		{
			return this->nid;
		}

		Task * task(void) const
		{
			return this->nodeTask;
		}

		int inputSize(void) const
		{
			return this->inSize;
		}

		int outputSize(void) const
		{
			return this->outSize;
		}

		void setSubtreeSize(int s)
		{
			this->subSize = s;
		}

		int subtreeSize(void) const
		{
			return this->subSize;
		}

		int inputIsEmpty(void) const
		{
			return this->inIface->isEmpty();
		}

		const NodePtrVector & inputNodes(void) const
		{
			return this->inNodes;
		}

		const NodePtrVector & outputNodes(void) const
		{
			return this->outNodes;
		}

		void pushInput(int port, int id, const Argument & arg)
		{
			assert(this->inIface != 0);
			this->inIface->insert(port, id, arg);
		}

		void pushInput(const ArgumentArray & args)
		{
			assert(this->inIface != 0);
			this->inIface->insert(args);
		}

		bool setInput(int port, int id, const Argument & arg)
		{
			assert(this->inIface != 0);
			return this->inIface->set(port, id, arg);
		}

		/*
		bool getAvailableInputId(int & id)
		{
			assert(this->inIface != 0);
			return this->inIface->getId(id);
		}

		bool getAvailableInputIds(IntVector & ids)
		{
			assert(this->inIface != 0);
			return this->inIface->getIds(ids);
		}

		bool getAvailableInputById(ArgumentArray & args)
		{
			assert(this->inIface != 0);
			return this->inIface->extractInputById(args);
		}

		bool getAvailableInput(ArgumentArray & args)
		{
			assert(this->inIface != 0);
			return this->inIface->extractInput(args);
		}
		*/

		bool getAvailableInputById(ArgumentArray & args)
		{
			assert(this->inIface != 0);
			return this->inIface->extractInputById(args);
		}

		bool book(int id)
		{
			assert(this->inIface != 0);
			return this->inIface->book(id);
		}

		bool prepareToRun(int & id)
		{
			id = 0;

			int nid = 0;
			if (!this->inIface->getId(nid)) return false;
			bool booked = true;

			for (size_t i=0; i<this->outNodes.size(); ++i)
			{
				Node * n = this->outNodes[i];
				if (n == 0) continue;
				if (!n->book(nid))
				{
					booked = false;
					break;
				}
			}

			if   (booked) { id = nid;                    }
			else          { this->inIface->ungetId(nid); }

			return booked;
		}

		void feedOutput(const ArgumentArray & args)
		{
			assert(this->inIface != 0);
			assert(args.size() == this->outSize);
			const int id = args.id();
			for (size_t i=0; i<this->outLinks.size(); ++i)
			{
				NodeOutputLinkDescriptor & link = this->outLinks[i];
				Node * targetNode = link.targetNode();
				if (targetNode == 0) continue;
				const bool r = targetNode->setInput(link.targetPort(), id, args[link.sourcePort()]);
				assert(r);
				(void)r;
			}
		}

	protected:

		int                            nid;

		Task *                         nodeTask;

		int                            inSize;
		NodeInputInterface *           inIface;
		NodeInputLinkDescriptorVector  inLinks;
		NodePtrVector                  inNodes;

		int                            outSize;
		NodeOutputLinkDescriptorVector outLinks;
		NodePtrVector                  outNodes;

		int                            subSize;
};

}

}

#endif // DATAFLOW_DETAIL_NODE_H
